package plus.lgx.textsearcher.searcher.handler;

import plus.lgx.textsearcher.config.SearcherConfig;
import plus.lgx.textsearcher.entity.FileWrapper;
import plus.lgx.textsearcher.entity.PrintData;
import plus.lgx.textsearcher.searcher.extender.ExtenderEnum;
import plus.lgx.textsearcher.searcher.printer.BuiltinOutputHandler;
import plus.lgx.textsearcher.searcher.printer.HTMLOutputHandler;
import plus.lgx.textsearcher.searcher.printer.OutputHandler;
import plus.lgx.textsearcher.searcher.printer.TemplateOutputHandler;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.*;
import java.util.stream.Collectors;

import static plus.lgx.textsearcher.constant.Const.*;

/**
 * Class name: FileDispatcher<br>
 *
 * 文件调度类，分析文件的类型，分配给相应的文件处理类进行处理。
 *
 * <br>
 * Create time: 12/18/22 3:52 PM
 *
 * @author lgx
 * @version 1.0
 */
public class FileDispatcher {

    private final SearcherConfig config;

    private File output;

    private OutputHandler out;

    private final PrintData data = new PrintData();

//    private final Map<String, AbstractFileHandler> FILE_HANDLER_MAP = new HashMap<>();

    private final AbstractFileHandler plainTextFileHandler;

    public FileDispatcher(SearcherConfig config) throws IOException {
        this.config = config;
        data.searchText = config.searchContent;
        data.outputFileName = new File(config.outputFileName).getName();
        data.outputFilePath = new File(config.outputFileName).getAbsolutePath();
        createOutputFile();
        this.plainTextFileHandler = config.extenderEnum.equals(ExtenderEnum.LINE_)
                || config.extenderEnum.equals(ExtenderEnum.MULTILINE_)
                ? new LineHandler(config, out, data)
                : new PlainTextFileHandler(config, out, data);
    }

    public void createOutputFile() throws IOException {
        if (config.console) {
            out = config.template == null
                    ? config.html
                        ? config.searchContentRegex == null
                            ? new HTMLOutputHandler(data, System.out, config.ignoreCase)
                            : new HTMLOutputHandler(data, System.out, config.searchContentRegex)
                        : new BuiltinOutputHandler(data, System.out)
                    : new TemplateOutputHandler(new File(config.template), data, System.out);
            return;
        }
        if (output == null) {
            output = new File(config.outputFileName);
            if (output.exists() && !output.delete()) {
                System.err.println("Cannot delete the output file! ");
                System.exit(2);
            }

            String outputFileName = output.getAbsolutePath().startsWith(CURRENT_PATH)
                    ? output.getAbsolutePath().substring(CURRENT_PATH.length() + 1)
                    : output.getAbsolutePath();
            if (outputFileName.length() == 0) {
                outputFileName = ".";
            }

            if (output.createNewFile()) {
                System.out.format("Created output file \"%s\".%n", outputFileName);
                out = config.template == null
                        ? config.html
                          ? config.searchContentRegex == null
                            ? new HTMLOutputHandler(data, new PrintStream(output), config.ignoreCase)
                            : new HTMLOutputHandler(data, new PrintStream(output), config.searchContentRegex)
                          : new BuiltinOutputHandler(data, new PrintStream(output))
                        : new TemplateOutputHandler(new File(config.template), data, new PrintStream(output));
                return;
            }
            System.err.format("Cannot create output file \"%s\".%n", outputFileName);
            System.exit(2);
        }
    }

    public File[] getInputFiles() throws IOException {
        if (!config.console && output == null) {
            createOutputFile();
        }
        File dir = new File(config.searchDirectoryPath);
        if (!dir.exists()) {
            System.err.println("Directory not found: " + dir.getName());
            System.exit(6);
        }

        String outputFileName = dir.getAbsolutePath().equals(CURRENT_PATH)
                ? "."
                : dir.getAbsolutePath().startsWith(CURRENT_PATH)
                ? dir.getAbsolutePath().substring(CURRENT_PATH.length() + 1)
                : dir.getAbsolutePath();

        System.out.format("Obtaining files in directory \"%s\".%n", outputFileName);
        File[] files = getFileList(dir).toArray(new File[0]);
        if (files.length == 0) {
            System.err.format("Cannot find any file in directory \"%s\".%n", outputFileName);
            System.exit(1);
        }
        if (files.length > 10000) {
            System.out.println("A large number of files are found, if you want to stop, use \"Ctrl + C\". ");
        }
        return sort(files);
    }

    private List<File> getFileList(File dir) {
        File[] all = Optional.ofNullable(dir.listFiles()).orElse(new File[0]);
        if (all.length == 0) {
            return new ArrayList<>(0);
        }
        List<File> fs = Arrays.stream(all).filter(File::isFile).filter(f -> {
            String fileName = f.getName();
            String path = f.getAbsolutePath();
            boolean isOutputFile = output != null && path.equals(output.getAbsolutePath()),
                    isThisCommandFile = THIS_COMMAND_FILE_PATH.equals(path),
                    inWhitelist = config.whitelist == null || config.whitelist.matcher(fileName).matches(),
                    inBlacklist = config.whitelist == null && config.blacklist != null
                            && config.blacklist.matcher(fileName).matches();
            return !(isOutputFile || isThisCommandFile || !inWhitelist || inBlacklist);
        }).collect(Collectors.toList());
        File[] ds = Arrays.stream(all).filter(File::isDirectory).toArray(File[]::new);
        int innerFileCount = 0;
        List<List<File>> innerFiles = new ArrayList<>(ds.length);
        for (File f : ds) {
            List<File> fileList = getFileList(f);
            innerFileCount += fileList.size();
            innerFiles.add(fileList);
        }
        List<File> ret = new ArrayList<>(fs.size() + innerFileCount);
        ret.addAll(fs);
        innerFiles.forEach(ret::addAll);
        return ret;
    }

    private File[] sort(File[] files) {

        if (config.randomize) {
            Random r = new Random();
            int len = files.length;

            for(int i = 0; i < len; ++i) {
                int ind = r.nextInt(len);
                File f = files[ind];
                files[ind] = files[i];
                files[i] = f;
            }
        } else if (config.sort != null) {
            files = Arrays.stream(files).sorted(config.sort).toArray(File[]::new);
        }
        return files;

    }

    // TODO
    public void dispatch(FileWrapper f) {
        plainTextFileHandler.handle(f);
    }

    public void printHead() {
        out.printHead();
    }

    public void printTail() {
        out.printTail();
        try {
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
